//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <type_traits>
#include <utility>
#include <functional>
#include <locale>
#include <iterator>
#include <algorithm>
#include "axe_trait.h"
#include "axe_composite.h"
#include "axe_terminal.h"
#include "axe_iterator.h"
#include "axe_detail.h"
#include "axe_operator.h"

namespace axe
{

    //-------------------------------------------------------------------------
    // functions for composite rules
    //-------------------------------------------------------------------------

    /// function r_many matches specified rule, separated by another rule specified number of occurrences
    template<class R, class S>
    inline
		r_many_t<detail::enable_if_rule<R>, detail::enable_if_rule<S>>
        r_many(R&& r, S&& s, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<std::decay_t<R>, std::decay_t<S>>(std::forward<R>(r), std::forward<S>(s), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    template<class R>
    inline
        r_many_t<detail::enable_if_rule<R>, r_empty>
        r_many(R&& r, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<std::decay_t<R>, r_empty>(std::forward<R>(r), r_empty{}, min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// function r_ref creates a reference wrapper rule
    template<class R>
    inline
        r_ref_t<R>
        r_ref(R&& r)
    {
        return r_ref_t<R>(std::forward<R>(r));
    }

    //-------------------------------------------------------------------------
    /// function r_find skips input elements until specified rule is matched
    template<class R>
    inline
        r_find_t<detail::enable_if_rule<R>>
        r_find(R&& r)
    {
        return r_find_t<std::decay_t<R>>(std::forward<R>(r));
    }

    //-------------------------------------------------------------------------
    /// function r_fail wraps failure function, which is executed on rule failure (e.g. rule | r_fail(fail_func))
    template<class F>
    inline
        r_fail_wrapper_t<std::decay_t<F>>
        r_fail(F&& f)
    {
        return r_fail_wrapper_t<std::decay_t<F>>(std::forward<F>(f));
    }

    //-------------------------------------------------------------------------
    inline r_fail_wrapper_t<throw_fail_t> r_fail(std::string str)
    {
        return r_fail_wrapper_t<throw_fail_t>(throw_fail_t(std::move(str)));
    }

    //-------------------------------------------------------------------------
    inline r_fail_wrapper_t<throw_fail_t> r_fail(const char* str = "r_fail")
    {
        return r_fail_wrapper_t<throw_fail_t>(throw_fail_t(str));
    }

    //-------------------------------------------------------------------------
    /// function r_select selects rule to match based on result of first rule matching 
    /// r_select(r1, r2, r3) is equivalent to !r1 & r3 | r1 & r2, except r1 is matched only once
    template<class R1, class R2, class R3>
    inline
        r_select_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>, detail::enable_if_rule<R3>>
        r_select(R1&& r1, R2&& r2, R3&& r3)
    {
        return r_select_t<std::decay_t<R1>, std::decay_t<R2>, std::decay_t<R3>>(std::forward<R1>(r1), std::forward<R2>(r2), std::forward<R3>(r3));
    }

    //-----------------------------------------------------------------------------
    /// r_test matches the specified rule, but always returns the initial iterator
    /// thus it allows to test the rule and perform semantic actions
    //-----------------------------------------------------------------------------
    template<class R>
    inline
        r_test_t<detail::enable_if_rule<R>>
        r_test(R&& r) 
    { 
        return r_test_t<std::decay_t<R>>(std::forward<R>(r)); 
    }

    //-------------------------------------------------------------------------
    // r_skip creates skip rule from rule r and predicate f
    //-------------------------------------------------------------------------
    template<class R, class F>
    inline
        std::enable_if_t< AXE_IS_RULE(R) && (AXE_IS_PREDICATE(F) || AXE_IS_RULE(F)), r_skip_t<std::decay_t<R>, std::decay_t<F>> >
        r_skip(R&& r, F&& f)
    {
        return r_skip_t<std::decay_t<R>, std::decay_t<F>>(std::forward<R>(r), std::forward<F>(f));
    }

    //-------------------------------------------------------------------------
    // r_skip creates skip rule from rule r and character c
    //-------------------------------------------------------------------------
    template<class R, class charT>
    inline
        std::enable_if_t<AXE_IS_RULE(R) && std::is_integral<charT>::value, r_skip_t<std::decay_t<R>, axe::is_char_t<charT>>>
        r_skip(R&& r, charT c)
    {
        return r_skip_t<std::decay_t<R>, axe::is_char_t<charT>>(std::forward<R>(r), axe::is_char_t<charT>(c));
    }

    //-------------------------------------------------------------------------
    // r_skip creates skip rule from rule r and any character from str
    //-------------------------------------------------------------------------
    template<class R, class charT>
    inline
        std::enable_if_t<AXE_IS_RULE(R) && std::is_integral<charT>::value, r_skip_t<std::decay_t<R>, axe::is_any_t<const charT*>>>
        r_skip(R&& r, const charT* str)
    {
        return r_skip_t<std::decay_t<R>, axe::is_any_t<const charT*>>(std::forward<R>(r),
            axe::is_any_t<const charT*>(str));
    }

    //-------------------------------------------------------------------------
    // r_convert creates a new rule that converts iterator according to function
    //-------------------------------------------------------------------------
    template<class R, class F>
    inline
        std::enable_if_t<AXE_IS_RULE(R), r_convert_t<std::decay_t<R>, std::decay_t<F>>>
        r_convert(R&& r, F&& f)
    {
        return r_convert_t<std::decay_t<R>, std::decay_t<F>>(std::forward<R>(r), std::forward<F>(f));
    }

    //-------------------------------------------------------------------------
    // r_ucase/r_lcase create rules that convert iterator values to upper/lower case
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------------------
    template<class charT>
    inline
        r_convert_t<axe::r_str<charT>, detail::toupper<charT>>
        r_ucase(const charT* str, std::locale loc = std::locale())
    {
        return r_convert_t<axe::r_str<charT>, detail::toupper<charT>>(axe::r_str<charT>(str),
            detail::toupper<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    template<class charT>
    inline
        r_convert_t<axe::r_str<std::basic_string<charT>>, detail::toupper<charT>>
        r_ucase(std::basic_string<charT> str, std::locale loc = std::locale())
    {
        return r_convert_t<axe::r_str<std::basic_string<charT>>, detail::toupper<charT>>(
            axe::r_str<std::basic_string<charT>>(std::move(str)),
            detail::toupper<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    template<class charT>
    inline
        r_convert_t<axe::r_str<charT>, detail::tolower<charT>>
        r_lcase(const charT* str, std::locale loc = std::locale())
    {
        return r_convert_t<axe::r_str<charT>, detail::tolower<charT>>(axe::r_str<charT>(str),
            detail::tolower<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    template<class charT>
    r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>
        r_lcase(std::basic_string<charT> str, std::locale loc = std::locale())
    {
        return r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>(
            axe::r_str<std::basic_string<charT>>(std::move(str)),
            detail::tolower<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    // ignore case converts everything to lower case
    template<class charT>
    inline
        r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>
        r_icase(const charT* str, std::locale loc = std::locale())
    {
        std::basic_string<charT> lstr(detail::lcase<charT>(str, loc));
        return r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>(
            axe::r_str<std::basic_string<charT>>(std::move(lstr)),
            detail::tolower<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    // ignore case converts everything to lower case
    template<class charT>
    inline
        r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>
        r_icase(std::basic_string<charT> str, std::locale loc = std::locale())
    {
        std::basic_string<charT> lstr(detail::lcase<charT>(std::move(str), loc));
        return r_convert_t<axe::r_str<std::basic_string<charT>>, detail::tolower<charT>>(
            axe::r_str<std::basic_string<charT>>(std::move(lstr)),
            detail::tolower<charT>(std::move(loc)));
    }

    //-------------------------------------------------------------------------
    // r_buffered creates a new rule that buffers iterator (e.g. input iterator)
    //-------------------------------------------------------------------------
    template<class R>
    inline
        r_buffered_t < detail::enable_if_rule<R> > r_buffered(R&& r)
    {
        return r_buffered_t<std::decay_t<R>>(std::forward<R>(r));
    }

    //-------------------------------------------------------------------------
    // r_named creates a named rule with the same semantics
    //-------------------------------------------------------------------------
    template<class R>
    inline
        r_named_t<detail::enable_if_rule<R>> r_named(R&& r, const char* name)
    {
        return r_named_t<std::decay_t<R>>(std::forward<R>(r), name);
    }

} // namespace axe

